/*
 * @Author: xiaosihan 
 * @Date: 2024-05-25 05:18:49 
 * @Last Modified by: xiaosihan
 * @Last Modified time: 2024-05-25 16:40:15
 */

import { flatten, get, isNumber } from "lodash";
import { Box3, BufferGeometry, DoubleSide, Group, Mesh, MeshBasicMaterial, MeshLambertMaterial, Shape, ShapeGeometry, Vector3 } from "three";
import * as BufferGeometryUtils from "three/examples/jsm/utils/BufferGeometryUtils";
import { degToRad } from "three/src/math/MathUtils";
import ExtrudeMesh, { ExtrudeMeshProps } from "three-base/ExtrudeMesh";

type Coords = number[] | Coords[];

type Geojson = {
    type: string,
    features: Array<{
        type: string,
        properties: {
            id?: string,
            adcode?: any,
            size?: string,
            name?: string,
            cp?: [number, number],
            childNum?: 9
        },
        geometry: {
            type: string, // 'Point' | 'MultiPoint' | 'LineString' | 'MultiLineString' | 'Polygon' | 'MultiPolygon',
            coordinates: Coords
        }
    }>
}

// 地图模型
export default class GeojsonMesh extends Group {

    constructor(geojson: Geojson, thickness = 0) {
        super();
        this.init(geojson, thickness);
    }

    size = new Vector3(1, 1, 1);

    //bottomGroup
    bottomGroup = (() => {
        const bottomGroup = new Group();
        this.add(bottomGroup);
        return bottomGroup;
    })();

    //top
    topGroup = (() => {
        const topGroup = new Group();
        this.add(topGroup);
        return topGroup;
    })();

    //sideGroup
    sideGroup = (() => {
        const sideGroup = new Group();
        this.add(sideGroup);
        return sideGroup;
    })();

    init(geojson: Geojson, thickness = 0) {
        geojson.features.map((f, index) => {
            const { adcode: code = "unkonw", name = "unkonw" } = f.properties;
            let points = f.geometry.coordinates as number[][][];
            if (!isNumber(get(points, [0, 0, 0]))) {
                //@ts-ignore
                points = flatten(points);
            }
            let geometry!: BufferGeometry;
            for (let i = 0; i < points.length; i++) {
                let shape = new Shape();
                for (let ii = 0; ii < points[i].length; ii++) {
                    const [x, y] = points[i][ii];
                    if (ii === 0) {
                        shape.moveTo(x, y);
                    } else {
                        shape.lineTo(x, y);
                    }
                }
                if (!geometry) {
                    geometry = new ShapeGeometry(shape);
                } else {
                    geometry = BufferGeometryUtils.mergeGeometries([
                        geometry,
                        new ShapeGeometry(shape)
                    ]);
                }
            }

            geometry.rotateX(degToRad(-90));

            // 每一个区的模型对象
            const areaMesh = new Mesh(geometry, new MeshBasicMaterial({
                side: DoubleSide
            }));

            areaMesh.userData = { name, code };
            areaMesh.uuid = code;
            areaMesh.name = name;
            this.bottomGroup.add(areaMesh);
        });

        // 计算每个模型的uv的值, 并移动到中心点
        const box3 = new Box3();
        const center = new Vector3();
        box3.setFromObject(this.bottomGroup);
        box3.getSize(this.size);
        this.size.setY(thickness);
        box3.getCenter(center);
        const sideLength = Math.max(this.size.x, this.size.z);

        this.traverse(obj => {
            const mesh = obj as Mesh;

            if (mesh.isMesh) {
                const position = mesh.geometry.getAttribute("position");
                const uv = mesh.geometry.getAttribute("uv");

                for (let i = 0; i < position.count; i++) {
                    uv.setX(i, (position.getX(i) + sideLength / 2) / sideLength);
                    uv.setY(i, 1 - (position.getZ(i) + sideLength / 2) / sideLength);
                }
                mesh.geometry.setAttribute("uv", uv);
            }
        });

        // 如果设置了厚度就加一个侧面模型
        if (thickness > 0) {
            this.bottomGroup.traverse(obj => {
                const mesh = obj as Mesh;
                if (mesh.isMesh) {
                    const newMesh = mesh.clone();
                    newMesh.geometry = newMesh.geometry.clone();
                    newMesh.geometry.translate(0, thickness, 0);
                    this.topGroup.add(newMesh);
                }
            });
        }

        // 每个区的侧面模型
        if (thickness > 0) {

            // 岩层成挤出路径
            const rockMeshoptions: ExtrudeMeshProps = {
                paths: [
                    // { vector3: [-304, 0.1, 250], radius: 1, segments: 1, scale: { x: 1, y: 1 }, name: "" },
                ],
                shapePath: [
                    [0, thickness],
                    [0, 0],
                ],
                closed: true
            };

            // 提取每个区的地图轮廓
            geojson.features.map((f, index) => {
                let points = f.geometry.coordinates as number[][][];
                const { adcode: code = "unkonw", name = "unkonw" } = f.properties;
                if (!isNumber(get(points, [0, 0, 0]))) {
                    //@ts-ignore
                    points = flatten(points);
                }

                for (let i = 0; i < points.length; i++) {
                    rockMeshoptions.paths = [];
                    for (let ii = 0; ii < points[i].length; ii++) {
                        const [x, y] = points[i][ii];

                        rockMeshoptions.paths.push({
                            vector3: [x, 0, y * -1],
                            radius: 1,
                            segments: 0,
                            scale: { x: 1, y: 1 },
                            name: ""
                        });
                    }

                    // 创建岩石层对象
                    const rockMesh = new ExtrudeMesh(rockMeshoptions, new MeshLambertMaterial({
                        side: DoubleSide
                    }));
                    rockMesh.uuid = code;
                    rockMesh.name = name;
                    this.sideGroup.add(rockMesh);
                }
            })
        }

    }

}